-- This file is part of SUIT, copyright (c) 2016 Matthias Richter

local BASE = (...):match('(.-)[^%.]+$')
local utf8 = require 'utf8'

local function split(str, pos)
	local offset = utf8.offset(str, pos) or 0
	return str:sub(1, offset-1), str:sub(offset)
end

local function dividLines(text)
    local lines = {}
    local textLen = utf8.len(text)
    local sol = 1
    local eol = 1
    local u = 1
    for i = 1,textLen do
        local char = string.sub(text,utf8.offset(text,i),utf8.offset(text,i+1)-1)
        if char == "\n" then
            local line = {sol = sol,eol = eol -1,sol_u = u,eol_u = i - 1}
            table.insert(lines,line)
            sol = eol + 1
            eol = sol
            u = i + 1
        else
            eol = eol + char:len()
        end
    end
    local line = {sol = sol,eol = eol,sol_u = u,eol_u = textLen}
    table.insert(lines,line)
    return lines
end

local punct = {
   ["【"] = "[",
   ["】"] = "]",
   ["，"] = ",",
   ["；"] = ";",
   ["（"] = "(",
   ["）"] = ")",
   ["“"] = "\"",
   ["”"] = "\"",
   ["："] = ":",
    }

return function(core, input, ...)
	local opt, x,y,w,h = core.getOptionsAndSize(...)
	opt.id = opt.id or input
	opt.font = opt.font or love.graphics.getFont()
    
	local text_width = opt.font:getWidth(input.text)
	local fontHeight = opt.font:getHeight()
    w = w or text_width + 6
	h = h or opt.font:getHeight() + 4
	input.text = input.text or ""
    if input.pretext or input.reset then
        input.text = input.pretext or input.text
        input.pretext = nil
        input.reset = nil
        input.lines = dividLines(input.text)
        opt.offset_y = math.huge
        if input.lineLimit and #input.lines>input.lineLimit then
           input.text = string.sub(input.text,
               input.lines[#input.lines-input.lineLimit+1].sol,
               input.lines[#input.lines].eol)
           input.lines = dividLines(input.text)
        end
        input.cursory = #input.lines
        input.cursorx = input.lines[input.cursory].sol_u
        opt.offset_y = (input.cursory-1)*opt.font:getHeight()
    end
	local wm = w - 6
    local text = input.text
    local textLen = utf8.len(text)
    input.lines = input.lines or dividLines(text)
    
    local lineCount = #input.lines
    input.cursory = math.max(1, math.min(lineCount, 
            input.cursory or lineCount))
    input.cursorx = math.max(input.lines[input.cursory].sol_u,
            math.min (input.lines[input.cursory].eol_u + 1, 
            input.cursorx or input.lines[input.cursory].eol_u + 1))
    
	opt.cursor_posx = opt.font:getWidth(
        text:sub(input.lines[input.cursory].sol,
            utf8.offset(text,input.cursorx) -1)
    )
    opt.cursor_posy = 1 + (opt.font:getHeight()+1)*(input.cursory-1)
    
	
	-- user interaction
	if input.forcefocus ~= nil and input.forcefocus then
		core.active = opt.id
		input.forcefocus = false
	end

	opt.state = core:registerHitbox(opt.id, x,y,w,h)
	opt.hasKeyboardFocus = core:grabKeyboardFocus(opt.id)

	if (core.candidate_text.text == "") and opt.hasKeyboardFocus then
		local keycode,char = core:getPressedKey()
		-- text input
		if char and char ~= "" then
			for cn,en in pairs(punct) do
                char = string.gsub(char,cn,en)
            end
            local a,b = split(input.text, input.cursorx)
			input.text = table.concat{a, char, b}
			input.cursorx = input.cursorx + utf8.len(char)
            input.lines = dividLines(input.text)
		end

		-- text editing
		if keycode == 'backspace' then
			local a,b = split(input.text, input.cursorx)
			input.text = table.concat{split(a,utf8.len(a)), b}
			input.cursorx = math.max(1, input.cursorx-1)
            input.lines = dividLines(input.text)
		elseif keycode == 'delete' then
			local a,b = split(input.text, input.cursorx)
			local _,b = split(b, 2)
			input.text = table.concat{a, b}
		end

		-- cursor movement
		if keycode =='left' then
			input.cursorx = math.max(0, input.cursorx-1)
		elseif keycode =='right' then -- cursor movement
			input.cursorx = math.min(utf8.len(input.text)+1, input.cursorx+1)
		elseif keycode =='home' then -- cursor movement
			input.cursorx = 1
            input.cursory = 1
		elseif keycode =='end' then -- cursor movement
			input.cursorx = input.lines[lineCount].eol_u+1
            input.cursory = #input.lines
		elseif keycode == "up" then
            local oy = input.cursory
            input.cursory = math.max(1, input.cursory-1)
            input.cursorx = input.cursorx - input.lines[oy].sol_u +
                input.lines[input.cursory].sol_u
        elseif keycode == "down" then
            local oy = input.cursory
            input.cursory = math.min(#input.lines, input.cursory+1)
            input.cursorx = input.cursorx - input.lines[oy].sol_u +
                input.lines[input.cursory].sol_u
        elseif keycode == "return" and not opt.mono then
            local char = "\n"
            local a,b = split(input.text, input.cursorx)
			input.text = table.concat{a, char, b}
			input.lines = dividLines(input.text)
            input.cursory = input.cursory + 1
            input.cursorx = input.lines[input.cursory].sol_u
            opt.offset_y = (input.cursory-1)*opt.font:getHeight()
        elseif keycode == "tab" then
            local char = "  "
            local a,b = split(input.text, input.cursorx)
			input.text = table.concat{a, char, b}
			input.lines = dividLines(input.text)
            input.cursorx = input.cursorx + 2
        elseif keycode == "v" and love.keyboard.isDown("lctrl") then
            local char = love.system.getClipboardText( )
            local a,b = split(input.text, input.cursorx)
			input.text = table.concat{a, char, b}
			input.lines = dividLines(input.text)
        end

		-- move cursor position with mouse when clicked on
		local md = core:mouseReleasedOn(opt.id)
        local mx,my =core:getMousePosition()
        if md then
            local offset = opt.linenum and 22 or 2
            local tx = mx - x + input.text_draw_offset - offset
            local ty = my - y - 2 + opt.offset_y
            input.cursory = math.max(1,math.min(
                    math.ceil(ty/opt.font:getHeight()),#input.lines))
            input.cursorx = input.lines[input.cursory].eol_u + 1
            for c = input.lines[input.cursory].sol_u,
                input.lines[input.cursory].eol_u+1 do
                local s = input.text:sub(input.lines[input.cursory].sol,
                    utf8.offset(input.text, c)-1)
                if opt.font:getWidth(s) >= tx then
                    input.cursorx = c - 1
                    break
                end
            end
        end
	end

	
	local drawfunc
    if opt.nodraw then
        drawfunc = function() (opt.draw or core.theme.Input)(input, opt, x,y,w,h) end
    else
        core:registerDraw(opt.draw or core.theme.Input, input, opt, x,y,w,h)
    end
	return {
        draw = drawfunc,
		id = opt.id,
		hit = core:mouseReleasedOn(opt.id),
		submitted = core:keyPressedOn(opt.id, "return"),
		hovered = core:isHovered(opt.id),
		entered = core:isHovered(opt.id) and not core:wasHovered(opt.id),
		left = not core:isHovered(opt.id) and core:wasHovered(opt.id)
	}
end
